# -*- coding: utf-8 -*-
#*******************************************************************************
#   
#   解析器程序的公用部分                                  
#                                                                                         
#*******************************************************************************
from utils import *

#---------------------------- 验证模块 ----------------------------

# 所有的解析模块包含此函数，用来检验是否符合当前模块
def url_judgement(url):
    return False

#---------------------------- 数据格式 ----------------------------

# 图片信息
class InfoPic(object):
    def __init__(self,vol=None,url='',title=''):
        self.vol=vol                    # 图片所属于的卷
        self.url=url                    # 图片链接地址
        self.title=title                # 图片名字,可以作为图片的编号
    def __str__(self):
        return "Picture Informations\nvols:{}\nurl:{}\ntitle:{}".format(self.vol.title,self.url,self.title)

# 卷信息
class InfoVol(object):
    def __init__(self,url='',title='',cover=None):
        self.url=url                    # 卷链接
        self.title=title                # 卷名
        self.cover=cover                # 卷封面，保存网络地址
    def __str__(self):
        return "Volume Information\nurl:{}\ntitle:{}\ncover:{}".format(self.url,self.title,self.cover)

# 漫画信息
class InfoComic(object):
    def __init__(self,url=None,title='no_title',cover=None,description='No description'):
        self.url=url                    # 漫画链接地址
        self.title=title                # 漫画主题、名字
        self.cover=cover                # 漫画封面
        self.description=description    # 漫画描述信息
        self.vols=[]                    # 漫画所拥有的的所有卷
    def __str__(self):
        return "Comic Information\ntitle:{}\ndescription:{}\ncover:{}\nurl:{}\n".format(self.title,self.description,self.cover,self.url)

#---------------------------- 下载器类 ----------------------------

# 漫画下载器父类，提供通用功能
# 提供整个漫画的下载信息
# 下载漫画
class ComicLoader(object):
    # 对于每个下载器需要提供自己的：漫画首页解析器(comic_page_parser)和漫画卷页面解析器(comic_vol_parser)
    def __init__(self,url,comic_page_parser,comic_vol_parser):
        self.comic=InfoComic(url)                       # 下载器需要下载的漫画
        # 下载器-漫画
        self.comic_dir=''                               # 下载到本地的漫画的保存地址
        self.comic_url=url                              # 需要下载的漫画的地址
        self.comic_cover_path=''                        # 漫画保存在本地的封面地址
        # 卷相关
        self.down_vols=[]                               # 要下载的卷
        self.down_vols_number=0                         # 要下载的卷的卷数
        self.index_vol=0                                # 将要下载的卷的下标计数器
        self.index_vol_mutex=threading.Lock()           # 将要下载的卷的下标线程锁
        self.load_vols=0                                # 已经下载的卷的计数器
        self.load_vols_mutex=threading.Lock()           # 已经下载的卷的计数线程锁
        self.load_vols_over=False                       # 卷下载完成标志
        # 漫画图片页相关
        self.pics=[]                                    # 需要下载的漫画图片
        self.pics_number=0                              # 需要下载漫画图片数量
        self.index_pic=0                                # 将要下载的漫画图片下标
        self.index_pic_mutex=threading.Lock()           # 将要下载的漫画图片下标线程锁
        self.load_pics=0                                # 已经下载的漫画图片的计数器
        self.load_pics_mutex=threading.Lock()           # 已经下载的漫画图片的计数线程锁
        # 标志
        self.pics_down_over=False                       # 图片下载完成标志
        self.comic_down_over=False                      # 漫画下载完成标志
        # 解析器
        self.comic_page_parser=comic_page_parser        # 漫画首页解析器
        self.comic_vol_parser=comic_vol_parser          # 漫画卷页面解析器
        # 编码格式
        self.page_encoding='UTF-8'                      # 漫画首页编码格式
        self.vol_encoding='UTF-8'                       # 漫画卷页编码格式

        # ----------------------------  下载流程  ----------------------------

    # 解析漫画首页，获取漫画信息
    # 获取漫画首页内容
    # 解析漫画获得的信息：主题、封面、简介、卷信息
    # 下载封面图片
    def parse(self):
        self.parse_comic()
        self.create_comic_dir()
        self.cover_down()
        print('parse work over')

    # 下载UI界面选定的卷
    # 下载指定的卷页面，解析卷的信息
    # 下载漫画页
    def work(self):
        self.down_vols_work()
        self.down_pics_work()

    # UI界面调用这个接口添加要下载的卷
    def add_down_vols(self,vol):
        self.down_vols.append(vol)
        self.down_vols_number+=1

    # ----------------------------  reset工作  ----------------------------

    # 下载完成之后重设标志
    def reset(self):
        # 卷相关信息
        self.down_vols=[]
        self.down_vols_number=0
        self.index_vol=0
        self.load_vols=0
        self.load_vols_over=False
        # 漫画页
        self.pics=[]
        self.pics_number=0
        self.index_pic=0
        self.load_pics=0
        # 标志
        self.pics_down_over=False
        self.comic_down_over=False
        
    #---------------------------- 工作函数 ----------------------------

    # 解析漫画首页
    # 1.下载漫画
    # 2.调用解析器解析
    def parse_comic(self):
        content=request_for(self.comic_url,common_headers).decode(self.page_encoding,'ignore')
        # 这里调用解析器，解析器补充整个漫画信息
        cp=self.comic_page_parser(self.comic)
        cp.work(content)

    # 创建漫画目录
    # 1.检查是否存在download目录，不存在则自己创建
    # 2.创建漫画主题为名称的目录：名字不符合创建目录的条件时，用时间作为参数创建目录名字
    def create_comic_dir(self):
        comic=self.comic
        cur_path=os.getcwd()+'\\'+'download'
        if os.path.isdir(cur_path):
            pass
        else:
            os.mkdir(cur_path)
        # 如果漫画名字漫画名字直接用时间戳
        if comic.title=='no_title':
            comic.title=title_by_time()
        comic_path=cur_path+'\\'+comic.title
        if os.path.isdir(comic_path):
            pass                        # 目录已经存在
        else:
            try:
                os.mkdir(comic_path)    # 如果漫画名不符合目录名字规则
            except Exception as err:
                print('[** file name illegal **]',err)
                title=title_by_time()
                comic_path=cur_path+'\\'+title
                os.mkdir(comic_path)
        self.comic_dir=comic_path

    # 通过封面地址获取封面
    # 名字不合法的情况下直接命名为：cover
    def cover_down(self):
        comic=self.comic
        img_url=comic.cover#img_url=comic.cover.url
        print('cover img url',img_url)
        # 获取图片后缀：为了防止名字不合法的情况
        for i in range(len(img_url)-1,-1,-1):
            if img_url[i]=='.':
                postfix=img_url[i:]
                break
        print('cover postfix',postfix)
        img_name=comic.title+postfix
        img_path=self.comic_dir+'\\'+img_name
        # 创建图片
        try:
            img=open(img_path,'wb')
        except Exception as err:
            print('[** cover name is not illegal **]',err)
            img_path=self.comic_dir+'\cover'+postfix
            print('new img_name',)
        else:
            img.close()
        # 获取图片数据
        img_content=self.cover_down_request(img_url)
        with open(img_path,'wb') as img:
            img.write(img_content)
        self.comic_cover_path=img_path
        print('img path:',img_path)

    # 创建请求的头部
    # 在这个创建过程中，提供referer信息
    def image_referer_headers(self,referer_url=None):
        if referer_url:
            common_headers.update({'Referer':referer_url})
        return common_headers

    # 下载封面图片，默认提供漫画页作为referer
    def cover_down_request(self,img_url):
        headers=self.image_referer_headers(self.comic_url)
        content=request_for(img_url,headers)
        return content

    # 漫画图片下载,默认提供卷地址作为referer
    def pics_down_request(self,pic,vol):
        headers=self.image_referer_headers(vol.url)
        content=request_for(pic.url,headers)
        return content
        
    # 下载卷，根据卷名来创建文件夹
    # 如果卷名非法，则使用卷的id命名
    def down_vols_work(self):
        comic_path=self.comic_dir
        for vol in self.down_vols:
            vol_path=comic_path+'\\'+vol.title
            if os.path.isdir(vol_path):
                pass
            else:
                try:
                    os.mkdir(vol_path)
                except Exception as err:
                    vol.title=title_by_time()
                    vol_path=comic_path+'\\'+str(vol.title)
                    os.mkdir(vol_path)
        # 启动线程下载解析卷
        # 各自拥有解析器解析自己的卷
        thread_num=8 if 8<self.down_vols_number else self.down_vols_number
        for i in range(0,thread_num):
            th=ParserVolThread(self)
            th.setDaemon(True)
            th.start()
            
    # 下载所有的漫画图片页
    # 1.建立线程观察是否解析漫画卷信息完成
    # 2.启动线程下载漫画页
    def down_pics_work(self):
        dpt=DownPicsThread(self)
        dpt.setDaemon(True)
        dpt.start()

#---------------------------- 工作线程 ----------------------------

# 卷解析线程
# 下载卷页面，解析卷页面，获取漫画页信息，漫画页地址整合
class ParserVolThread(threading.Thread):
    def __init__(self,loader=None):
        threading.Thread.__init__(self)
        self.loader=loader
    # 线程工作
    def run(self):
        loader,comic=self.loader,self.loader.comic
        # 获取卷地址
        while loader.index_vol<loader.down_vols_number:
            loader.index_vol_mutex.acquire()
            if loader.index_vol>=loader.down_vols_number:
                loader.index_vol_mutex.release()
                return
            vol=loader.down_vols[loader.index_vol]
            self.name='thread '+vol.title+' '+vol.url
            loader.index_vol+=1
            loader.index_vol_mutex.release()
            # 获取卷信息
            content=request_for(vol.url,loader.image_referer_headers(loader.comic_url)).decode(loader.vol_encoding,'ignore')
            vp=loader.comic_vol_parser()
            vp.work(content)
            # 获得返回的图片地址:地址列表格式 [ (url,title)  ]
            pics_url=[]
            for url in vp.pic_urls:
                pics_url.append(InfoPic(vol,url[0],url[1]))
            # 图片链接获取完成,加入总列表
            loader.load_vols_mutex.acquire()
            if vol.cover:                           # 这里加入封面图片
                loader.pics.append(vol.cover)
            loader.pics.extend(pics_url)
            loader.load_vols+=1
            if loader.load_vols>=loader.down_vols_number:
                loader.load_vols_over=True
                loader.pics_number=len(loader.pics)
            loader.load_vols_mutex.release()

# 漫画图片下载线程
class PerPicsDownThread(threading.Thread):
    def __init__(self,loader):
        threading.Thread.__init__(self)
        self.loader=loader
    # 开始下载过程
    def run(self):
        loader,comic=self.loader,self.loader.comic
        while loader.index_pic<loader.pics_number:
            loader.index_pic_mutex.acquire()
            if loader.index_pic>=loader.pics_number:
                loader.index_pic_mutex.release()
            self.name='PerPicsDownThread '+str(loader.index_pic)
            pic=loader.pics[loader.index_pic]
            loader.index_pic+=1
            loader.index_pic_mutex.release()
            url=pic.url
            if '.jpg' in url:   postfix='.jpg'
            elif '.png' in url: postfix='.png'
            pic_path=loader.comic_dir+'\\'+pic.vol.title+'\\'+pic.title
            content=self.loader.pics_down_request(pic,pic.vol)
            with open(pic_path,'wb') as img:
                img.write(content)
            loader.load_pics_mutex.acquire()
            loader.load_pics+=1
            loader.load_pics_mutex.release()

# 漫画页下载启动线程
# 观察卷下载解析是否完成
# 创建漫画页下载线程
# 监视下载
class DownPicsThread(threading.Thread):
    def __init__(self,loader):
        threading.Thread.__init__(self)
        self.loader=loader
    # 工作启动
    def run(self):
        loader,comic=self.loader,self.loader.comic
        # 监视卷下载情况
        while loader.load_vols<loader.down_vols_number:
            # 等待
            pass
        print('vols info collect over')
        print('total pics:',loader.pics_number)
        thread_num=9 if 9<loader.pics_number else loader.pics_number
        for i in range(0,thread_num):
            ppd=PerPicsDownThread(loader)
            ppd.setDaemon(True)
            ppd.start()
        # 监视下载情况
        while loader.load_pics<loader.pics_number:
            # 监视下载情况
            pass
        loader.comic_down_over=True
        print('down pics over')

#----------------------------  解析器 ----------------------------

# 漫画首页解析器
class ComicPageParser(HTMLParser):
    def __init__(self,comic=None):
        HTMLParser.__init__(self)
        self.comic=comic
    # 解析工作
    def work(self,content):
        # 具体实现
        pass

class ComicVolParser(HTMLParser):
    def __init__(self):
        HTMLParser.__init__(self)
        self.pic_index=1
        self.pic_urls=[]
    # 解析工作
    def work(self,content):
        # 各自具体实现
        pass
